package com.droidbank.model;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import android.app.Application;
import android.content.Context;
import android.os.Environment;
import android.util.Log;

public class DataModel extends Application
{
	//all acco1unts
	private final Vector<Account> 
		accounts = new Vector<Account>();
	
	//glocal types (expense)
	private static Vector<String> globalTypes = new
		Vector<String>();
	
	
	public static boolean isDirty = false;
	public static boolean isSaved = false;
	public static boolean isDataLoaded = false;
	
	private XMLFileFormat xmlLoader;
	
	public static String FILE_NAME, FILE_NAME_SD;
	public static String FILE_NAME_BACKUP;
	
	public static String ACCOUNT_INDEX = "accountIndex";
	
	
	//listeners
//	private static final Vector<ITransactionOccured> transactionListeners = 
//		new Vector<ITransactionOccured>();
//	
//	private static final Vector<IAccountsChanged> accountListeners = 
//		new Vector<IAccountsChanged>();
//	
//	private static final Vector<INetDataReceivedListener> netDataListeners = 
//		new Vector<INetDataReceivedListener>();
//	
	
	
	public void onCreate()
	{
		super.onCreate();
		
		xmlLoader = new XMLFileFormat();
		
		FILE_NAME = "main.xml";
		FILE_NAME_BACKUP = FILE_NAME + ".bk";
		FILE_NAME_SD = Environment.getExternalStorageDirectory() + "/" + FILE_NAME;
	}


	//----------------------------------
	
	public Vector<Account> getAccounts()
	{
		return accounts;
	}
	
	public ArrayList<Account> getAccountsAL()
	{
		return new ArrayList<Account>(accounts);
	}
	
	public String getXML()
	{
		return xmlLoader.getXMLAsString();
	}
	
	public String load()
	{
		if(isDataLoaded)
			return "";
		
		Log.d("MODEL", "LOAD- loading");
		
		isDataLoaded=true;
		
		return xmlLoader.loadXMLFile(FILE_NAME, true, false);
	}
	
	public String loadStringXML(String s)
	{
		return xmlLoader.loadXMLFile(s, false, false);
	}
	
	public String loadFile(String fileName, boolean fromSD)
	{
		return xmlLoader.loadXMLFile(fileName, true, fromSD);
	}
	
	
	public void importFile(String fileName)
	{
		System.out.println(fileName + " Imported");
		xmlLoader.diffDocuementFromCurrent(fileName, true);
	}
	
	public void importData(String data)
	{
		xmlLoader.diffDocuementFromCurrent(data, false);
	}
	
	
	public boolean save()
	{
		return saveFile(FILE_NAME, false, false);
	}
	
	public boolean saveFile(String fileName, boolean ignoreDirty, boolean saveSd)
	{
		Log.d("MODEL", "SAVE- saving");
		
		if(!ignoreDirty){
		
		if(!isDirty){
			return true;
		}
		
		isSaved = true;
		isDirty = false;
		
		}
		
		return xmlLoader.saveXMLFile(fileName, saveSd);
	}
	
	
	//--------------------------------------------
	
	
	public void clearAllState()
	{
		accounts.removeAllElements();
		//DataModel.fireExpensesUpdated();
		DataModel.isDirty = true;
	}
	
	
	public static void addExpenseTypeToList(String type)
	{
		boolean contains = false;
		for(String t: globalTypes)
		{
			if(type.equalsIgnoreCase(t))
			{
				contains=true;
			}
		}
		
		if(!contains){
			String val = type.toUpperCase().charAt(0) + type.substring(1).toLowerCase();
			globalTypes.add(val);
			Collections.sort(globalTypes);
		}	
	}
	
	public static Vector<String> getExpenseTypes()
	{
		return globalTypes;
	}
	
	
	public Vector<String> getListOfNamesFromType(String type)
	{
		Vector<String> names = new Vector<String>();
		
		String typel = type.toLowerCase();
		
		for(Account a: accounts)
		{
			for(Expense e: a.getExpenses())
			{
				if(e.getType().toLowerCase().equals(typel))
				{
					String nameUpper = Character.toUpperCase(e.getName().charAt(0)) + e.getName().substring(1).toLowerCase();
					
					if(!names.contains(nameUpper))
					{
						names.add(nameUpper);
					}
				}
			}
		}
		
		Collections.sort(names);
		
		return names;
	}
	
	
	//-----------------------listener stuff
	
//	public static void addNetDataListener(INetDataReceivedListener dl)
//	{
//		netDataListeners.add(dl);
//	}
//	
//	public static void removeNetDataListener(INetDataReceivedListener dl)
//	{
//		netDataListeners.remove(dl);
//	}
//	
//	
//	public static void addAccountsListener(IAccountsChanged ac)
//	{
//		accountListeners.add(ac);
//	}
//	
//	public static void removeAccountsListener(IAccountsChanged ac)
//	{
//		accountListeners.remove(ac);
//	}
//	
//	
//	
//	public static void addTransactionListener(ITransactionOccured ito)
//	{
//		transactionListeners.add(ito);
//	}
//	
//	public static void removeTransactionListener(ITransactionOccured ito)
//	{
//		transactionListeners.remove(ito);
//	}
//	
//	public static void fireAccountAdded(Account a)
//	{
//		for(IAccountsChanged ito: accountListeners)
//			ito.accountAdded(a);
//		
//		isDirty=true;
//	}
//	
//	public static void fireAccountRemoved(Account a)
//	{
//		for(IAccountsChanged ito: accountListeners)
//			ito.accountRemoved(a);
//		
//		isDirty=true;
//	}
//	
//	public static void fireTransfeOccured(String fromAcc, String toAcc, Expense from, Expense to)
//	{
//		for(ITransactionOccured ito: transactionListeners)
//			ito.transferOccured(fromAcc, toAcc, from, to);
//		
//		isDirty=true;
//	}
//	
//	
//	public static void fireExpenseAdded(Expense e)
//	{
//		for(ITransactionOccured ito: transactionListeners)
//			ito.expenseAdded(e, null);
//		
//		isDirty=true;
//	}
//	
//	public static void fireExpenseAdded(Expense e, String a)
//	{
//		for(ITransactionOccured ito: transactionListeners)
//			ito.expenseAdded(e, a);
//		
//		isDirty=true;
//	}
//	
//	public static void fireExpenseRemoved(Expense e)
//	{
//		for(ITransactionOccured ito: transactionListeners)
//			ito.expenseRemoved(e, null);
//		
//		isDirty=true;
//	}
//	
//	public static void fireExpensesUpdated()
//	{
//		for(ITransactionOccured ito: transactionListeners)
//			ito.expensesUpdated();
//	}
//	
//	public static void fireNetDataReceived(byte[] data)
//	{
//		for(INetDataReceivedListener ndl:netDataListeners)
//			ndl.netDataReceived(data);
//	}
	
	
	private class XMLFileFormat
	{
		private static final String DATA = "Data";
		private static final String NAME = "name";
		private static final String ACCOUNT = "Account";
		private static final String EXPENSE = "Expense"; 
		private static final String TYPE = "type";
		private static final String AMOUNT = "amount";
		private static final String DATE = "date";
		private static final String OFFSET = "offset";
		
		
		public String getXMLAsString()
		{
			Document doc = getCurrentXML();
			if(doc == null){return null;}
			
			StringWriter sw = new StringWriter();
			
	        // Use a Transformer for output
	        TransformerFactory transformerFactory = TransformerFactory.newInstance();
	        Transformer transformer;
	        try {
	            transformer = transformerFactory.newTransformer();
	        }
	        catch (TransformerConfigurationException e) {
	        	System.out.println(e);
				return null;
	        }
	        DOMSource source = new DOMSource(doc);
	        StreamResult result = new StreamResult(sw);
	        // transform source into result will do save
	        try {
	            transformer.transform(source, result);
	        }
	        catch (TransformerException e) {
	        	System.out.println(e);
				return null;
	       }
	        
	        return sw.toString().trim();
		}
		
		
		public boolean saveXMLFile(String fileName, boolean saveSD)
		{
			Document doc = getCurrentXML();
			if(doc == null){return false;}
			
			//save xml to file
	        FileOutputStream fos;
	        Transformer transformer;
	        try {
	        	
	        	if(saveSD)
	        	{
	        		fos = new FileOutputStream(new File(fileName));
	        	}
	        	else
	        		fos = openFileOutput(fileName,Context.MODE_WORLD_READABLE);
	        }
	        catch (FileNotFoundException e) {
				return false;
	        }
	        // Use a Transformer for output
	        TransformerFactory transformerFactory = TransformerFactory.newInstance();
	        try {
	            transformer = transformerFactory.newTransformer();
	        }
	        catch (TransformerConfigurationException e) {
				return false;
	        }
	        DOMSource source = new DOMSource(doc);
	        StreamResult result = new StreamResult(fos);
	        // transform source into result will do save
	        try {
	            transformer.transform(source, result);
	        }
	        catch (TransformerException e) {
				return false;
	       }
	        
	        return true;
		}
		
		
		public Document getCurrentXML()
		{
			Document doc= null;
			   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			   DocumentBuilder parser;
			try {
				parser = factory.newDocumentBuilder();
				doc = parser.newDocument();
			} catch (ParserConfigurationException e) {
				System.out.println(e);
				return null;
			}
			
			Node root = doc.appendChild(doc.createElement(DATA));
			
			//add all the accounts
			for(Account a: accounts)
			{
				Node accountNode = root.appendChild(doc.createElement(ACCOUNT));
				((Element)accountNode).setAttribute(NAME, a.getName()); //write name
				if(a.getStartingAmount() != 0) //write offset amount if present
				((Element)accountNode).setAttribute(OFFSET, String.valueOf(a.getStartingAmount()));
				
				for(Expense e: a.getExpenses())
				{
					Node eNode = accountNode.appendChild(doc.createElement(EXPENSE));
					
					((Element)eNode).setAttribute(AMOUNT, String.valueOf(e.getAmount()));
					((Element)eNode).setAttribute(TYPE, e.getType());
					((Element)eNode).setAttribute(NAME, e.getName());
					((Element)eNode).setAttribute(DATE, String.valueOf(e.getDate()));
				}
				
			}
			
			return doc;
		}
		
		
		
		public String loadXMLFile(String string, boolean stringAsFileName, boolean loadFromSD)
		{
			Document doc= null;
			   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			   DocumentBuilder parser;
			try {
				parser = factory.newDocumentBuilder();
				
				if(stringAsFileName)
				{
					if(loadFromSD)
					{
						doc = parser.parse(new File(string));
					}
					else
						doc = parser.parse(new InputSource(openFileInput(string)));
				}
					
				else
					doc = parser.parse(new ByteArrayInputStream(string.getBytes()));
				
				
			} catch (ParserConfigurationException e) {
				return e.toString();
			} catch (SAXException e) {
				return e.toString();
			} catch (IOException e) {
				return e.toString();
			}
			
			for(Account a: parse(doc))
				accounts.add(a);
			
			return "";
		}
		
		
	private Vector<Account> parse(Document doc)
	{
		Vector<Account> addedAccounts = new Vector<Account>();
		NodeList nl = doc.getDocumentElement().getChildNodes();
		for(int i=0 ; i < nl.getLength(); i++)
		{
			if(nl.item(i).getNodeType() == Node.ELEMENT_NODE)
			{
				if(nl.item(i).getNodeName().equals(ACCOUNT))
				{
					Node n = nl.item(i).getAttributes().getNamedItem(NAME);
					Node offset = nl.item(i).getAttributes().getNamedItem(OFFSET);
					
					if(n != null){
					
						Account account	= null;
						if(offset != null)	
							account = new Account(n.getNodeValue(), Double.valueOf(offset.getNodeValue()));
						else
							account = new Account(n.getNodeValue());
					
					NodeList exps = nl.item(i).getChildNodes();
					for(int j=0; j < exps.getLength(); j++)
					{
						if(exps.item(j).getNodeType() == Node.ELEMENT_NODE)
						{
						  
						  if(exps.item(j).getNodeName().equals(EXPENSE))
						  {
							Node type  = exps.item(j).getAttributes().getNamedItem(TYPE);
							Node amount  = exps.item(j).getAttributes().getNamedItem(AMOUNT);
							Node name  = exps.item(j).getAttributes().getNamedItem(NAME);
							Node date  = exps.item(j).getAttributes().getNamedItem(DATE);
							
							if(type != null && amount != null && name != null && date != null)
							{
								Expense e = new Expense(
										(long)Double.valueOf(date.getNodeValue()).doubleValue(), type.getNodeValue(),
										name.getNodeValue(), "", Double.valueOf(amount.getNodeValue()).doubleValue());
								
								account.addExpense(e);

								addExpenseTypeToList(e.getType());
								
							}
							}
						}
					}
					addedAccounts.add(account);
					}
				}
			}
		}
		
		return addedAccounts;
		
	
	}
	
	
	
	private void diffDocuementFromCurrent(String string, boolean stringAsFileName)
	{
		Document doc= null;
		   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		   DocumentBuilder parser;
		try {
			parser = factory.newDocumentBuilder();
			
			if(stringAsFileName)
				doc = parser.parse(new File(string));
			else
				doc = parser.parse(new ByteArrayInputStream(string.getBytes()));
			
			
		} catch (ParserConfigurationException e) {
			System.out.println(e);
			return;
		} catch (SAXException e) {
			System.out.println(e);
			return;
		} catch (IOException e) {
			System.out.println(e);
			return;
		}
		
		
		
		Vector<Account> newAccounts = parse(doc);
		
		for(Account a: newAccounts)
		{
			boolean exists = false;
			
			for(Account a2: accounts)
			{
				if(a.getName().equals(a2.getName()))
				{
					exists = true;
					
					//compare expenses
					for(Expense e: a.getExpenses())
					{
						if(!a2.containsExpense(e))
						{
							//find difference //more fancy
							a2.addExpense(e);
						}
						
					}
				}
					
			}
			
			//add the new account if dosnt exist
			if(!exists)
				accounts.add(a);
		}
		
		DataModel.isDirty = true;
		//fireExpensesUpdated();
	
	}
	
}
}
